<!doctype html>
<html lang="zh-cn">
<head>

    <meta charset="utf-8">
    <meta name="generator" content="Hugo 0.57.2" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <title>Redis 学习 | The Sky of OtsWang</title>
    <meta property="og:title" content="Redis 学习 - The Sky of OtsWang">
    <meta property="og:type" content="article">
        
    <meta property="article:published_time" content="2019-02-12T14:17:06&#43;08:00">
        
        
    <meta property="article:modified_time" content="2019-04-08T10:20:42&#43;08:00">
        
    <meta name="Keywords" content="golang,go语言,otswang,java,博客,python">
    <meta name="description" content="Redis 学习">
        
    <meta name="author" content="OtsWang">
    <meta property="og:url" content="https://otswang.gitee.io/hugo/post/middle_tools/redis/">
    <link rel="shortcut icon" href="/favicon.ico" type="image/x-icon">

    <link rel="stylesheet" href="/hugo/css/normalize.css">
    
        <link rel="stylesheet" href="/hugo/css/prism.css">
    
    <link rel="stylesheet" href="/hugo/css/style.css">
    <script type="text/javascript" src="//cdn.bootcss.com/jquery/3.2.1/jquery.min.js"></script>

    


    
    
</head>

<body>
<header id="header" class="clearfix">
    <div class="container">
        <div class="col-group">
            <div class="site-name ">
                
                    <a id="logo" href="https://otswang.gitee.io/hugo/">
                        The Sky of OtsWang
                    </a>
                
                <p class="description">擅长写HelloWorld的小小码农</p>
            </div>
            <div>
                <nav id="nav-menu" class="clearfix">
                    
                    
                    <a  href="https://otswang.gitee.io/hugo/" title="Home">Home</a>
                    
                    <a  href="https://otswang.gitee.io/hugo/tags/" title="Tags">Tags</a>
                    
                    <a  href="https://otswang.gitee.io/hugo/categories/" title="Categories">Categories</a>
                    
                    <a  href="https://otswang.gitee.io/hugo/archives/" title="Archives">Archives</a>
                    
                    <a  href="https://otswang.gitee.io/hugo/about/" title="About">About</a>
                    
                </nav>
            </div>
        </div>
    </div>
</header>


<div id="body">
    <div class="container">
        <div class="col-group">

            <div class="col-8" id="main">
                <div class="res-cons">
                    <article class="post">
                        <header>
                            <h1 class="post-title">Redis 学习</h1>
                        </header>
                        <date class="post-meta meta-date">
                            2019年2月12日
                        </date>
                        
                        <div class="post-meta">
                            <span>|</span>
                            
                                <span class="meta-category"><a href="https://otswang.gitee.io/hugo/categories/middletools">MiddleTools</a></span>
                            
                        </div>
                        
                        
                        
                        <div class="post-content">
                            <p>Redis学习</p>

<h2 id="redis-内存划分">redis 内存划分</h2>

<p>redis的内存占用主要是：</p>

<ul>
<li>数据： Redis在存储对象时，并不是直接将数据扔进内存，而是会对对象进行各种包装：如redisObject、SDS等</li>
<li>进程运行时需要的内存</li>
<li>缓冲内存</li>
<li>内存碎片： 可通过安全重启来减少内存占用</li>
</ul>

<h2 id="基本数据类型与操作">基本数据类型与操作</h2>

<ul>
<li>String, 可以是字符串，整型或者浮点数</li>
<li>List, 双边列表</li>
<li>Set, 无序唯一</li>
<li>Hash, key-value 对, 不重复的key</li>
<li>ZSet, 有序key-value 对</li>
</ul>

<p>String的操作;</p>

<ul>
<li>get</li>
<li>set</li>
<li>del</li>
</ul>

<p>使用场景：</p>

<p>缓存功能：</p>

<p>字符串最经典的使用场景，redis最为缓存层，Mysql作为储存层，绝大部分请求数据都是redis中获取，由于redis具有支撑高并发特性，所以缓存通常能起到加速读写和降低后端压力的作用。</p>

<p>计数器：</p>

<p>许多运用都会使用redis作为计数的基础工具，他可以实现快速计数、查询缓存的功能，同时数据可以一步落地到其他的数据源。如：视频播放数系统就是使用redis作为视频播放数计数的基础组件。</p>

<p>共享session：</p>

<p>出于负载均衡的考虑，分布式服务会将用户信息的访问均衡到不同服务器上，用户刷新一次访问可能会需要重新登录，为避免这个问题可以用redis将用户session集中管理，在这种模式下只要保证redis的高可用和扩展性的，每次获取用户更新或查询登录信息都直接从redis中集中获取。</p>

<p>限速：</p>

<p>处于安全考虑，每次进行登录时让用户输入手机验证码，为了短信接口不被频繁访问，会限制用户每分钟获取验证码的频率。</p>

<p>List 的操作</p>

<ul>
<li>lpush</li>
<li>rpush</li>
<li>lpop</li>
<li>rpop</li>
<li>lindex</li>
<li>lrange</li>
</ul>

<p>使用场景：</p>

<p>消息队列： redis的lpush+brpop命令组合即可实现阻塞队列，生产者客户端是用lupsh从列表左侧插入元素，多个消费者客户端使用brpop命令阻塞时的“抢”列表尾部的元素，多个客户端保证了消费的负载均衡和高可用性</p>

<p>Set 的操作</p>

<ul>
<li>sadd</li>
<li>sismember</li>
<li>smembers</li>
<li>srem</li>
</ul>

<p>使用场景：</p>

<p>标签（tag）：集合类型比较典型的使用场景，如一个用户对娱乐、体育比较感兴趣，另一个可能对新闻感兴趣，这些兴趣就是标签，有了这些数据就可以得到同一标签的人，以及用户的共同爱好的标签，这些数据对于用户体验以及曾强用户粘度比较重要。（用户和标签的关系维护应该放在一个事物内执行，防止部分命令失败造成数据不一致）</p>

<p>sadd=tagging（标签）</p>

<p>spop/srandmember=random item（生成随机数，比如抽奖）</p>

<p>sadd+sinter=social Graph(社交需求)</p>

<p>Hash 的操作</p>

<ul>
<li>hset</li>
<li>hget</li>
<li>hdel</li>
<li>hgetall</li>
</ul>

<p>使用场景:</p>

<p>哈希结构相对于字符串序列化缓存信息更加直观，并且在更新操作上更加便捷。所以常常用于<strong>用户信息</strong>等管理，但是哈希类型和关系型数据库有所不同，哈希类型是稀疏的，而关系型数据库是完全结构化的，关系型数据库可以做复杂的关系查询，而redis去模拟关系型复杂查询开发困难，维护成本高。</p>

<p>zset 的操作</p>

<ul>
<li>zadd</li>
<li>zrange</li>
<li>zrangebyscore [withscores] [limit offset count]</li>
<li>zrem</li>
</ul>

<p>使用场景：</p>

<p>排行榜：有序集合经典使用场景。例如视频网站需要对用户上传的视频做排行榜，榜单维护可能是多方面：按照时间、按照播放量、按照获得的赞数等。</p>

<p>redis五种数据类型的使用场景：[<a href="https://blog.csdn.net/qq_41654985/article/details/82390875">https://blog.csdn.net/qq_41654985/article/details/82390875</a>]</p>

<h2 id="redis数据结构">Redis数据结构</h2>

<p><strong>简单动态字符串：SDS</strong></p>

<p>结构：</p>

<ul>
<li>len: 字符串长度</li>
<li>alloc: 字符串的最大容量</li>
<li>flags: 占用1字节，其中低3位表示header的类型</li>
<li>buf： 字符数组</li>
</ul>

<p>如果len 小于 1M，则alloc = 2*len， 如果len 大于1M，则alloc = len + 1M。</p>

<p><strong>链表：list</strong></p>

<p>Redis中的链表为双向链表。</p>

<p>一个链表结构，包含了</p>

<ul>
<li>head，指向第一个元素，其pre为null</li>
<li>tail， 指向最后一个元素，其next为null</li>
<li>len， 链表的长度</li>
<li>dup， 函数复制链表节点所保存的值，用于深度拷贝</li>
<li>free， 函数用于释放节点保存的值，</li>
<li>match， 对比节点的值是否和另一个值相等</li>
</ul>

<p><strong>字典：dictht</strong></p>

<p>Redis使用MurmurHash2 算法计算键的hash值，u使用链地址法来解决冲突，被分配到同一个索引的多个键值对会连接成一个单向链表。</p>

<ul>
<li>table 数组，每个元素可能为链表</li>
<li>size</li>
<li>sizemask</li>
<li>userd</li>
</ul>

<p><strong>跳跃表：zskiplist</strong></p>

<p>以有序的方式在层次化的链表中保存元素，效率和平衡树媲美&ndash; 查找、删除、添加的时间复杂度为log(n)，并且相比平衡树来说，更简单。</p>

<p><img src="https://user-gold-cdn.xitu.io/2019/6/17/16b614b3fe5016a7?imageslim" /></p>

<p><strong>整数集合：intset</strong></p>

<p>整数集合 intset 是集合对象的底层实现之一，当一个集合只包含整数值元素，并且这个集合的元素数量不多时， Redis 就会使用整数集合作为集合对象的底层实现。</p>

<p><strong>压缩列表：ziplist</strong></p>

<p>压缩队列 ziplist 是列表对象和哈希对象的底层实现之一。当满足一定条件时，列表对象和哈希对象都会以压缩队列为底层实现。</p>

<p><strong>对象</strong></p>

<pre><code class="language-c">typedef struct redisObject {
    unsigned type:4;
    unsigned encoding:4;
    unsigned lru:LRU_BITS;
    int refcount;
    void *ptr;
} robj;
</code></pre>

<p>字符串对象的底层实现为SDS，列表对象的底层实现为ziplist或linkedlist，哈希对象的底层实现为ziplist或dict，集合对象的底层实现是intset或dict，有序集合对象的底层实现为ziplist或skiplist。</p>

<blockquote>
<p>参考：  <a href="https://juejin.im/post/5d067941f265da1bb13f30d2?utm_source=gold_browser_extension">https://juejin.im/post/5d067941f265da1bb13f30d2?utm_source=gold_browser_extension</a></p>
</blockquote>

<h2 id="数据持久化">数据持久化</h2>

<p>aof &amp; rdb</p>

<h2 id="redis集群与主从">redis集群与主从</h2>

<p>redis cluster在设计的时候，就考虑到了去中心化，去中间件，也就是说，集群中的每个节点都是平等的关系，都是对等的，每个节点都保存各自的数据和整个集群的状态。每个节点都和其他所有节点连接，而且这些连接保持活跃，这样就保证了我们只需要连接集群中的任意一个节点，就可以获取到其他节点的数据。</p>

<p>Redis 集群没有并使用传统的一致性哈希来分配数据，而是采用另外一种叫做哈希槽 (hash slot)的方式来分配的。redis cluster 默认分配了 16384 个slot，当我们set一个key 时，会用CRC16算法来取模得到所属的slot，然后将这个key 分到哈希槽区间的节点上，具体算法就是：CRC16(key) % 16384。所以我们在测试的时候看到set 和 get 的时候，直接跳转到了7000端口的节点。</p>

<p>Redis 集群会把数据存在一个 master 节点，然后在这个 master 和其对应的salve 之间进行数据同步。当读取数据时，也根据一致性哈希算法到对应的 master 节点获取数据。只有当一个master 挂掉之后，才会启动一个对应的 salve 节点，充当 master 。</p>

<p>需要注意的是：必须要3个或以上的主节点，否则在创建集群时会失败，并且当存活的主节点数小于总节点数的一半时，整个集群就无法提供服务了。</p>

<p>将16384 个hash slot 覆盖到所有节点上，对于存储的每个key值 使用CRC16(KEY)&amp;16348=slot 得到他对应的hash slot，并在访问key时就去找他的hash slot在哪一个节点上，然后由当前访问节点从实际被分配了这个hash slot的节点去取数据。节点之间使用轻量协议通信 减少带宽占用 性能很高 自动实现负载均衡与高可用 自动实现failover  并且支持动态扩展 官方已经玩到可以1000个节点 实现的复杂度低 总之个人比较喜欢这个架构 因为他的去中心化思想免去了proxy的消耗 是全新的思路</p>

<p>主从复制的步骤：</p>

<p>1、从服务器开始链接主服务器时，会向主服务器发送一个 SYNC 命令
2、主服务接收到命令之后，执行 BGSAVE，异步的将写命令保存到一个缓冲区里面
3、主服务器执行完BGSAVE之后，将.rdb 文件(BOF的持久化文件)发送给从服务器，从服务器从该文件恢复数据到内存中
4、主服务器还会以 Redis 命令协议的格式， 将写命令缓冲区中积累的所有内容都发送给从服务器，从服务器通过执行命令将数据恢复到内存中</p>

<p>redis 主从复制使用的是tcp通信协议，可以如下模拟：</p>

<pre><code class="language-bash">telnet host port
...
ping # 测试连通性
+pong # 回答

# 声明自己为从库
replconf listening-port port2
+OK # 回答

# 同步数据
sync
# 开始同步，定时发送ping 保持连通性
</code></pre>

<p>从redis一般不能进行写入数据，但是可以通过配置，<code>config set slave-read-only no</code>来配置可以.这就是数据迁移的一般原理。</p>

<h2 id="主从复制">主从复制</h2>

<p>当有需要使用到replication机制时，一般都会强烈建议把master的持久化开关打开。即使为了避免持久化带来的延迟影响，不把持久化开关打开，那么也应该把master配置为<strong>不会自动启动</strong>的。（避免当master重启时数据丢失传递到slave）</p>

<p>复制时，slave向master发送一个sync命令，部分复制时，发送一个psyn命令。master在内存中创建一个复制流队列，master和所有的slave都维护了一个队列的数据下标和master进程ID，当slave重连时，slave向master发送继续复制的命令psync，此时master会对比slave发送的进程ID和复制下标，如果下标可用，在部分复制，如果下标不可用，在全部复制。</p>

<p>Redis主从复制的基本步骤如下：</p>

<ol>
<li><p>建立主从关系，有如下三种方式：</p>

<ul>
<li>配置文件</li>
<li>启动参数</li>
<li>命令 slaveof host port <code>slaveof xxx 6379</code> or  <code>slaveof no one</code> 断开连接</li>
</ul>

<p>主从复制可以一主多从，但是从节点只能配置一个主节点，当重复配置主节点时会覆盖上一次的配置。</p>

<p>从节点一般是不允许写的，但是可以在配置中进行配置。</p></li>

<li><p>保存主节点信息：从节点将主节点的信息保存起来</p></li>

<li><p>通过定时函数，从节点发现一个主节点信息，开始进行创建连接，发送ping，进行身份校验，</p></li>

<li><p>从节点发送sysn或psync进行复制</p></li>

<li><p>主节点收到sync/psync 命令后，执行bgsave命令，并开始创建一个 复制积压缓冲区。</p>

<p>此复制积压缓冲区只有一个，对于所有从节点都一样的。</p></li>

<li><p>主节点将dump文件发送给从节点，从节点进行数据恢复。</p></li>

<li><p>主节点发送 ping 给 从节点，维持心跳（注意此处，官方文档中介绍是从节点发给主节点）</p></li>

<li><p>从节点发送 replconf_ack {offset} 进行回应，维持心跳</p></li>
</ol>

<p>Psync的几个重要概念：</p>

<ul>
<li><p>复制偏移量 offset</p>

<p>主从节点都在维护，如果主从的不一致，说明主从节点的数据不一致，如果offset 大于 复制积压缓冲区的大小，则无法进行pysnc</p></li>

<li><p>复制积压缓冲区</p>

<p>主节点维护一个队列，将每个写操作压入，如果offset大于此队列的大小，说明有写入操作被丢弃了，不能再进行pysnc</p></li>

<li><p>服务器run id</p>

<p>每个节点在启动时都会分配一个run ID，当执行slaveof操作时，从节点记下主节点的run ID，当下次进行psync操作时，如果此ID一致，说明从节点是主节点的从节点，可以（只是可以）进行psync。</p></li>
</ul>

<p>Psync 的步骤：</p>

<p><img src="https://user-gold-cdn.xitu.io/2018/7/2/16459978b3d7e0a9?imageView2/0/w/1280/h/960/format/webp/ignore-error/1" alt="Redis设计与实现的图片" /></p>

<h2 id="redis-哨兵模式">Redis 哨兵模式</h2>

<p>引入： 当主从中的主服务器宕机后，需要手动把一台从服务器切换为主服务器，需要人工干预，还会造成一段时间的不可用。</p>

<p>解决方案： 哨兵模式</p>

<h2 id="redis-与-lua">redis 与 lua</h2>

<p>待学习</p>

<h2 id="其他">其他</h2>

<h3 id="使用缓存中经常遇到的一些问题">使用缓存中经常遇到的一些问题</h3>

<p>缓存穿透： 查询数据库中本来就没的一个数据。 解决方法： 缓k空值。或者前面加过滤层。</p>

<p>缓存击穿： 热点key，在高并发情况下，此时key又恰好失效，会有大量请求打到db上。 解决方法： 第一次到达时使用互斥锁来锁住它。</p>

<p>缓存雪崩： 发生大规模缓存失效。 1 宕机： 分布式解决； 2 集中失效： 热点key失效时间在原基础上加随机值。 3 同一key： 互斥锁</p>

<h3 id="redis-string-的实现">redis string 的实现</h3>

<p>在redis中，strin并不是直接使用c语言的char[] 而是自己实现了个struct，如下：</p>

<pre><code class="language-c">struct sdshdr {
    unsigned int len;
    unsigned int free;
    char buf[];
};
</code></pre>

<p>其中的len为已经使用的空间大小，而free表示空闲大小，当buf的大小在1M以内时，free = len， 当len &gt; 1M 时，free = 1M。</p>

<pre><code class="language-c">#define SDS_MAX_PREALLOC (1024*1024)

// 具体实现
sds sdsMakeRoomFor(sds s, size_t addlen) {
    struct sdshdr *sh, *newsh;
    size_t free = sdsavail(s);
    size_t len, newlen;

    if (free &gt;= addlen) return s;
    len = sdslen(s);
    sh = (void*) (s-(sizeof(struct sdshdr)));
    newlen = (len+addlen);
    if (newlen &lt; SDS_MAX_PREALLOC) 
        newlen *= 2;
    else
        newlen += SDS_MAX_PREALLOC;
    newsh = zrealloc(sh, sizeof(struct sdshdr)+newlen+1);
    if (newsh == NULL) return NULL;

    newsh-&gt;free = newlen - len;
    return newsh-&gt;buf;
}

</code></pre>

<p>参考资料：</p>

<p><a href="https://juejin.im/entry/5b39c05df265da596e4cf399">https://juejin.im/entry/5b39c05df265da596e4cf399</a></p>

<p><a href="https://juejin.im/post/5c973584e51d455ecf51cb20">https://juejin.im/post/5c973584e51d455ecf51cb20</a></p>

<h2 id="常用命令">常用命令</h2>

<p>批量删除：</p>

<p>redis-cli -h host -p port -a pwd -n db keys &ldquo;*&rdquo; | xargs redis-cli -h host -p port -a pwd -n db del</p>
                        </div>

                        


                        


                        <div class="post-meta meta-tags">
                            
                            <ul class="clearfix">
                                
                                <li><a href="https://otswang.gitee.io/hugo/tags/redis">Redis</a></li>
                                
                            </ul>
                            
                        </div>
                    </article>
                    
    

    
    
                </div>
            </div>
            <div id="secondary">

    <section class="widget">
        <form id="search" action="//www.google.com/search" method="get" accept-charset="utf-8" target="_blank" _lpchecked="1">
      
      <input type="text" name="q" maxlength="20" placeholder="Search">
      <input type="hidden" name="sitesearch" value="https://otswang.gitee.io/hugo/">
      <button type="submit" class="submit icon-search"></button>
</form>
    </section>

    
    <div class="clear">
        <div class="toc-article">
            <div class="toc-title">文章目录</dixsv>
            <nav id="TableOfContents">
<ul>
<li>
<ul>
<li><a href="#redis-内存划分">redis 内存划分</a></li>
<li><a href="#基本数据类型与操作">基本数据类型与操作</a></li>
<li><a href="#redis数据结构">Redis数据结构</a></li>
<li><a href="#数据持久化">数据持久化</a></li>
<li><a href="#redis集群与主从">redis集群与主从</a></li>
<li><a href="#主从复制">主从复制</a></li>
<li><a href="#redis-哨兵模式">Redis 哨兵模式</a></li>
<li><a href="#redis-与-lua">redis 与 lua</a></li>
<li><a href="#其他">其他</a>
<ul>
<li><a href="#使用缓存中经常遇到的一些问题">使用缓存中经常遇到的一些问题</a></li>
<li><a href="#redis-string-的实现">redis string 的实现</a></li>
</ul></li>
<li><a href="#常用命令">常用命令</a></li>
</ul></li>
</ul>
</nav>
        </div>
    </div>
    

</div>
        </div>
    </div>
</div>
<footer id="footer">
    <div class="container">
        &copy; 2020 <a href="https://otswang.gitee.io/hugo/">The Sky of OtsWang By OtsWang</a>.
        Powered by <a rel="nofollow noreferer noopener" href="https://gohugo.io" target="_blank">Hugo</a>.
        <a href="https://www.flysnow.org/" target="_blank">Theme</a> based on <a href="https://github.com/Dudiao137/maupassant-hugo" target="_blank">maupassant-ots</a>.
        
    </div>
</footer>


    <script type="text/javascript">
    
    (function(){
        $("pre code").parent().addClass("line-numbers")
    }())

    window.MathJax = {
        tex2jax: {
            inlineMath: [ ['$','$'] ],
            processEscapes: true
        }
    };
    </script>
    <script type="text/javascript" src="/hugo/js/prism.js" async="true"></script>
    <script src='https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/MathJax.js?config=TeX-MML-AM_CHTML' async></script>

<a id="rocket" href="#top"></a>
<script type="text/javascript" src="/hugo/js/totop.js?v=0.0.0" async=""></script>







 
 <script src="https://mermaidjs.github.io/scripts/mermaid.min.js"></script>
 <script>
       mermaid.initialize({ startOnLoad: true });
 </script>
</body>
</html>
